package org.groupg.practice.document;

import org.mapdb.*;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

public class DocumentStore {
    private final DB db;
    private final HTreeMap<String, BusinessDocument> documentsMap;

    public DocumentStore(String dbPath) {
        // 配置数据库
        this.db = DBMaker.fileDB(new File(dbPath))
                .fileMmapEnableIfSupported() // 启用内存映射
                .transactionEnable()         // 启用事务
                .closeOnJvmShutdown()        // JVM关闭时自动关闭DB
                .make();

        // 创建或打开文档集合
        this.documentsMap = db.hashMap("documents")
                .keySerializer(Serializer.STRING)
                .valueSerializer(new BusinessDocumentSerializer())
                .createOrOpen();
    }

    // 保存文档
    public void saveDocument(BusinessDocument document) {
        String compositeKey = createCompositeKey(document.getType(), document.getId());
        documentsMap.put(compositeKey, document);
        db.commit();
    }

    // 获取文档
    public BusinessDocument getDocument(String type, String id) {
        String compositeKey = createCompositeKey(type, id);
        return documentsMap.get(compositeKey);
    }

    // 更新指标（带乐观锁）
    public boolean updateMetrics(String type, String id, Map<String, Object> metrics, long expectedVersion) {
        String compositeKey = createCompositeKey(type, id);
        BusinessDocument document = documentsMap.get(compositeKey);

        if (document == null || document.getVersion() != expectedVersion) {
            return false; // 版本冲突或文档不存在
        }

        // 合并新指标
        metrics.forEach((k, v) -> document.getMetrics().put(k, v));
        document.incrementVersion();
        documentsMap.put(compositeKey, document);
        db.commit();
        return true;
    }

    // 创建复合键：type:id
    private String createCompositeKey(String type, String id) {
        return type + ":" + id;
    }

    // 关闭数据库
    public void close() {
        db.close();
    }

    // 自定义序列化器（处理混合类型指标）
    static class BusinessDocumentSerializer implements Serializer<BusinessDocument> {
        @Override
        public void serialize(DataOutput2 out, BusinessDocument doc) throws IOException {
            out.writeUTF(doc.getId());
            out.writeUTF(doc.getType());
            out.writeLong(doc.getVersion());

            // 序列化指标
            Map<String, Object> metrics = doc.getMetrics();
            out.writeInt(metrics.size());
            for (Map.Entry<String, Object> entry : metrics.entrySet()) {
                out.writeUTF(entry.getKey());
                Object value = entry.getValue();

                if (value instanceof String) {
                    out.writeUTF("STRING");
                    out.writeUTF((String) value);
                } else if (value instanceof Integer) {
                    out.writeUTF("INTEGER");
                    out.writeInt((Integer) value);
                } else if (value instanceof Long) {
                    out.writeUTF("LONG");
                    out.writeLong((Long) value);
                } else if (value instanceof Double) {
                    out.writeUTF("DOUBLE");
                    out.writeDouble((Double) value);
                } else if (value instanceof Float) {
                    out.writeUTF("FLOAT");
                    out.writeFloat((Float) value);
                } else {
                    throw new IOException("Unsupported metric type: " + value.getClass());
                }
            }
        }

        @Override
        public BusinessDocument deserialize(DataInput2 in, int available) throws IOException {
            String id = in.readUTF();
            String type = in.readUTF();
            long version = in.readLong();

            BusinessDocument doc = new BusinessDocument(id, type);
//            doc.version = version;

            int metricsSize = in.readInt();
            for (int i = 0; i < metricsSize; i++) {
                String key = in.readUTF();
                String typeCode = in.readUTF();

                switch (typeCode) {
                    case "STRING":
                        doc.getMetrics().put(key, in.readUTF());
                        break;
                    case "INTEGER":
                        doc.getMetrics().put(key, in.readInt());
                        break;
                    case "LONG":
                        doc.getMetrics().put(key, in.readLong());
                        break;
                    case "DOUBLE":
                        doc.getMetrics().put(key, in.readDouble());
                        break;
                    case "FLOAT":
                        doc.getMetrics().put(key, in.readFloat());
                        break;
                    default:
                        throw new IOException("Unknown metric type: " + typeCode);
                }
            }
            return doc;
        }
    }
}